#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "UVisDataProvider.generated.h"

DECLARE_MULTICAST_DELEGATE_OneParam(FOnFloatProvided, float);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnIntProvided, int);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnBoolProvided, bool);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnStringProvided, FString);

/// <summary>
/// Actual Data Provider implementation (internal use only)
/// </summary>
/// <typeparam name="DataT">Type of data</typeparam>
/// <typeparam name="CallbackEventT">Delegate type of callback class</typeparam>
template<typename DataT, typename CallbackEventT>
requires std::derived_from<CallbackEventT, TMulticastDelegate<void(DataT)>>
class UTypedDataProvider_Internal 
{
public:
	void ProvideData(DataT data){ OnDataProvided_.Broadcast(data); }
	CallbackEventT& GetEvent() { return OnDataProvided_; }
protected:
	CallbackEventT OnDataProvided_;
};

///////// Data Provider for Both C++ and BPs:
UCLASS(Blueprintable)
class SIMUVISUALIZER_API UFloatDataProvider : public UObject
{
	GENERATED_BODY()
public:
	UFUNCTION(BlueprintCallable, Category = "Provider")
	void ProvideData(float value) { TypedProvider_.ProvideData(value); }
	FOnFloatProvided& GetEvent(){return TypedProvider_.GetEvent();}

	UTypedDataProvider_Internal<float, FOnFloatProvided>& GetInternalProvider() { return TypedProvider_; }
private:
	UTypedDataProvider_Internal<float, FOnFloatProvided> TypedProvider_;
};

UCLASS(Blueprintable)
class SIMUVISUALIZER_API UIntDataProvider : public UObject
{
	GENERATED_BODY()
public:
	UFUNCTION(BlueprintCallable, Category = "Provider")
	void ProvideData(int value) { TypedProvider_.ProvideData(value); }
	FOnIntProvided& GetEvent() { return TypedProvider_.GetEvent(); }

	UTypedDataProvider_Internal<int, FOnIntProvided>& GetInternalProvider() { return TypedProvider_; }
private:
	UTypedDataProvider_Internal<int, FOnIntProvided> TypedProvider_;
};

UCLASS(Blueprintable)
class SIMUVISUALIZER_API UBoolDataProvider : public UObject
{
	GENERATED_BODY()
public:
	UFUNCTION(BlueprintCallable, Category = "Provider")
	void ProvideData(bool value) { TypedProvider_.ProvideData(value); }
	FOnBoolProvided& GetEvent() { return TypedProvider_.GetEvent(); }

	UTypedDataProvider_Internal<bool, FOnBoolProvided>& GetInternalProvider() { return TypedProvider_; }
private:
	UTypedDataProvider_Internal<bool, FOnBoolProvided> TypedProvider_;
};

UCLASS(Blueprintable)
class SIMUVISUALIZER_API UStringDataProvider : public UObject
{
	GENERATED_BODY()
public:
	UFUNCTION(BlueprintCallable, Category = "Provider")
	void ProvideData(FString value) { TypedProvider_.ProvideData(value); }
	FOnStringProvided& GetEvent() { return TypedProvider_.GetEvent(); }

	UTypedDataProvider_Internal<FString, FOnStringProvided>& GetInternalProvider() { return TypedProvider_; }
private:
	UTypedDataProvider_Internal<FString, FOnStringProvided> TypedProvider_;
};
///////// end Data Provider for Both C++ and BPs ////////

template<typename DataT, typename CallbackEventT>
	requires std::derived_from<CallbackEventT, TMulticastDelegate<void(DataT)>>
class SIMUVISUALIZER_API FTypedDataProviderContainer
{
public:
	UTypedDataProvider_Internal<DataT, CallbackEventT>* GetProviderByIndex(int index);
private:
	TArray<UTypedDataProvider_Internal<DataT, CallbackEventT>*> Providers_;
};

class SIMUVISUALIZER_API UVisDataProvider
{
public:
	UTypedDataProvider_Internal<float, FOnFloatProvided>* GetFloatProviderByIndex(int index);
	UTypedDataProvider_Internal<int, FOnIntProvided>* GetIntProviderByIndex(int index);
	UTypedDataProvider_Internal<bool, FOnBoolProvided>* GetBoolProviderByIndex(int index);
	UTypedDataProvider_Internal<FString, FOnStringProvided>* GetStringProviderByIndex(int index);

	void ProvideFloatDataByIndex(int index, float data);
	void ProvideIntDataByIndex(int index, int data);
	void ProvideBoolDataByIndex(int index, bool data);
	void ProvideStringDataByIndex(int index, FString& data);

private:
	TUniquePtr< FTypedDataProviderContainer<float, FOnFloatProvided> > FloatProviders_;
	TUniquePtr< FTypedDataProviderContainer<int, FOnIntProvided> > IntProviders_;
	TUniquePtr< FTypedDataProviderContainer<bool, FOnBoolProvided> > BoolProviders_;
	TUniquePtr< FTypedDataProviderContainer<FString, FOnStringProvided>> StringProviders_;
};

UCLASS(Blueprintable, ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class SIMUVISUALIZER_API UVisDataProviderComponent: public UActorComponent
{
	GENERATED_BODY()

public:
	UVisDataProviderComponent(){}

	UFUNCTION(BlueprintCallable, Category = "Provider")
	void ProvideFloatDataByIndex(int index, float data);
	UFUNCTION(BlueprintCallable, Category = "Provider")
	void ProvideIntDataByIndex(int index, int data);
	UFUNCTION(BlueprintCallable, Category = "Provider")
	void ProvideBoolDataByIndex(int index, bool data);
	UFUNCTION(BlueprintCallable, Category = "Provider")
	void ProvideStringDataByIndex(int index, FString data);

	UTypedDataProvider_Internal<float, FOnFloatProvided>* GetFloatProviderByIndex(int index);
	UTypedDataProvider_Internal<int, FOnIntProvided>* GetIntProviderByIndex(int index);
	UTypedDataProvider_Internal<bool, FOnBoolProvided>* GetBoolProviderByIndex(int index);
	UTypedDataProvider_Internal<FString, FOnStringProvided>* GetStringProviderByIndex(int index);

protected:
	// Called when the game starts
	virtual void BeginPlay() override;
public:
	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
private:
	UVisDataProvider DataProvider_;
};

template<typename DataT, typename CallbackEventT>
	requires std::derived_from<CallbackEventT, TMulticastDelegate<void(DataT)>>
inline UTypedDataProvider_Internal<DataT, CallbackEventT>* FTypedDataProviderContainer<DataT, CallbackEventT>::GetProviderByIndex(int index)
{
	if (index < 0)
		return nullptr;

	if (Providers_.Num() <= index)
	{
		// extend array and create new provider:
		Providers_.SetNum(index + 1);
		Providers_[index] = new UTypedDataProvider_Internal<DataT, CallbackEventT>;
	}

	auto ptr = Providers_.GetData();
	return ptr[index];
}
